﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace HF
{
    public static partial class HExpr
    {
        public static partial class Property
        {
            /// <summary>
            /// 写入属性
            /// </summary>
            public static class Writer
            {
                private static readonly ConcurrentDictionary<Type, Lazy<(Action<object, object>, Delegate)>> _MapWrite = new();

                private static readonly ConcurrentDictionary<Type[], Lazy<(Action<object, object[], object>, Delegate)>> _MapWriteIndex = new();

                private static Type Complice(MemberInfo mem, out Action<object, object> write, out Delegate fastWrite)
                {
                    mem.CheckArgumentNull(nameof(mem));
                    Type dataType = mem.MemberType switch
                    {
                        MemberTypes.Property => ((PropertyInfo)mem).PropertyType,
                        MemberTypes.Field => ((FieldInfo)mem).FieldType,
                        _ => throw new Exception(),
                    };

                    var selfType = mem.DeclaringType;
                    {
                        //参数1
                        var p1Expr = Expression.Parameter(typeof(object), "p1");
                        
                        //参数2
                        var p2Expr = Expression.Parameter(typeof(object), "p2");

                        //局部变量   
                        var v1CExpre = ConvertExpr(p2Expr, dataType);

                        var v1c = selfType.IsValueType ?
                            Expression.Unbox(p1Expr, selfType) :
                            Expression.Convert(p1Expr, selfType);

                        var expr = Expression.Assign(Expression.PropertyOrField(v1c, mem.Name), v1CExpre);
                        var lambda = Expression.Lambda<Action<object, object>>(expr, new ParameterExpression[] { p1Expr, p2Expr });
                        write = lambda.Compile();
                    }

                    {
                        string funName;
                        Type paramType = selfType;
                        var isValueType = selfType.IsValueType;
                        if (isValueType)
                        {
                            funName = $"ActionRef<{GetTypeFullName(selfType)}, {dataType.Name}>";
                            paramType = selfType.MakeByRefType();
                        }
                        else
                        {
                            funName = $"Action<{GetTypeFullName(selfType)}, {dataType.Name}>";
                        }
                        
                        var p1Expr = Expression.Parameter(paramType, $"param_{selfType.Name}");
                        var p2Expr = Expression.Parameter(dataType, $"param_{dataType.Name}");

                        var memExpr = Expression.PropertyOrField(p1Expr, mem.Name);
                        var bodyExpr = Expression.Block(
                            Expression.Assign(memExpr, p2Expr),
                            Expression.Empty()
                        );

                        var et = GetDelegate(new[] { selfType, dataType }, true, isValueType);
                        var lambdaExpr = Expression.Lambda(et, bodyExpr, funName, new[] { p1Expr, p2Expr });
                        fastWrite = lambdaExpr.Compile();
                    }
                    return selfType;
                }

                private static Type CompliceStatic(MemberInfo mem, out Action<object, object> write, out Delegate fastWrite)
                {
                    Type dataType = mem is PropertyInfo proInfo ?
                        proInfo.PropertyType : (mem as FieldInfo).FieldType;

                    var selfType = mem.DeclaringType;
                    {
                        //参数2
                        var p2Expr = Expression.Parameter(typeof(object), "p2");
                        Expression p1CExpr = ConvertExpr(p2Expr, dataType);
                        var memExps = mem is PropertyInfo pi ?
                            Expression.Property(null, pi) : Expression.Field(null, mem as FieldInfo);

                        var assignExpr = Expression.Assign(memExps, p1CExpr);
                        var lambda = Expression.Lambda<Action<object, object>>(
                            assignExpr,
                            new[] { Expression.Parameter(typeof(object)), p2Expr });

                        write = lambda.Compile();
                    }

                    {
                        string funName = $"Action<{dataType.Name}>";
                        var p1Expr = Expression.Parameter(dataType, $"p1");
                        var p1CExpr = mem is PropertyInfo pi ?
                            Expression.Property(null, pi) : Expression.Field(null, mem as FieldInfo);

                        var bodyExpr = Expression.Block(
                            Expression.Assign(p1CExpr, p1Expr),
                            Expression.Empty()
                        );
                        var lambdaExpr = Expression.Lambda(bodyExpr, funName, new[] { p1Expr });
                        fastWrite = lambdaExpr.Compile();
                    }
                    return selfType;
                }

                private static Type CompliceIndex(PropertyInfo pro, out Action<object, object[], object> read, out Delegate fastRead)
                {
                    var indexArgs = pro.GetIndexParameters().Select(v => v.ParameterType).ToArray();
                    var selfType = pro.DeclaringType;
                    {
                        const string C_FunName = "Action<object, object[], object>";

                        //参数1 （object param_object）
                        var p1Expr = Expression.Parameter(typeof(object), "p1");

                        //参数2 （object[] params)
                        var p2Expr = Expression.Parameter(typeof(object[]), "p2");

                        //参数3 （object params)
                        var p3Expr = Expression.Parameter(typeof(object), "p3");

                        //参数1 转换表达式
                        var p1CExpr = Expression.Convert(p1Expr, selfType);

                        //参数2 转换表达式
                        var p2CExpr = new Expression[indexArgs.Length];
                        for (int i = 0; i < indexArgs.Length; i++)
                        {
                            var dataType = indexArgs[i];
                            Expression indexParameExpr = Expression.ArrayIndex(p2Expr, Expression.Constant(i, typeof(int)));
                            Expression convert = ConvertExpr(indexParameExpr, dataType);
                            p2CExpr[i] = convert;
                        }

                        //参数3 转换表达式
                        var p3CExpr = ConvertExpr(p3Expr, pro.PropertyType);

                        //赋值
                        var assign = Expression.Assign(Expression.MakeIndex(p1CExpr, pro, p2CExpr), p3CExpr);

                        var paramExprs = new ParameterExpression[] { p1Expr, p2Expr, p3Expr };
                        var lambdaExpr = Expression.Lambda<Action<object, object[], object>>(assign, C_FunName, paramExprs);
                        read = lambdaExpr.Compile();
                    }

                    {
                        bool isValueType = selfType.IsValueType;
                        var types = new Type[] { selfType }.Concat(indexArgs).Concat(new Type[] { pro.PropertyType }).ToArray();
                        Type et = GetDelegate(types, true, isValueType);
                        var method = pro.SetMethod;
                        fastRead = Delegate.CreateDelegate(et, method);
                    }
                    return selfType;
                }

                public static Type Complice(Type source, string proName, out Action<object, object> write, out Delegate fastWrite)
                {
                    var result = _MapWrite.GetOrAdd(source, v => newLazy(v));
                    write = result.Value.Item1;
                    fastWrite = result.Value.Item2;
                    return source;

                    Lazy<(Action<object, object>, Delegate)> newLazy(Type v) =>
                        new(() =>
                        {
                            var memInfo = GetPropertyField(source, proName, true, out var isStatic, out var returnType);
                            var tmp = isStatic ?
                                CompliceStatic(memInfo, out var rs1, out var rs2) :
                                Complice(memInfo, out rs1, out rs2);
                            return (rs1, rs2);
                        }
                        , LazyThreadSafetyMode.PublicationOnly);
                }

                /// <summary>
                /// 获取读取属性索引的函数
                /// </summary>
                /// <param name="source">属性所属的实体类型</param>
                /// <param name="paramTypes">索引参数类型集合</param>
                /// <param name="write">
                /// 返回读取索引的 <see cref="Func{T1, T2, TResult}"/> 函数
                /// <list type="bullet">
                /// <item>参数1 : <see cref="object"/> 待读取的实体对象</item>
                /// <item>参数2 : <see cref="object"/>[] 索引可以重载,用数组兼容</item>
                /// <item>返回 : <see cref="object"/> 对象</item>
                /// </list>
                /// </param>
                /// <param name="fastWrite">
                /// 返回高效读取索引的 <see cref="Delegate"/> 函数, 性能接近直接访问属性
                /// <para>因参数的类型、个数的不确定性需要强制转换后调用</para>
                /// <para>查看 <see cref="Delegate.Method"/>.Name 可以查看函数定义格式</para>
                /// </param>
                /// <returns></returns>
                public static Type Complice(Type source, Type[] paramTypes, out Action<object, object[], object> write, out Delegate fastWrite)
                {
                    source.CheckArgumentNull(nameof(source));
                    paramTypes.CheckArgumentNull(nameof(paramTypes));
                    if (paramTypes.Length == 0)
                    {
                        throw new ArgumentOutOfRangeException(nameof(paramTypes));
                    }

                    var key = new Type[] { source }.Concat(paramTypes).ToArray();
                    var result = _MapWriteIndex.GetOrAdd(key, v => newLazy(v[0]));
                    write = result.Value.Item1;
                    fastWrite = result.Value.Item2;
                    return source;

                    Lazy<(Action<object, object[], object>, Delegate)> newLazy(Type v) =>
                        new(() =>
                        {
                            var pro = GetIndexProperty(source, paramTypes, true);
                            CompliceIndex(pro, out var rs1, out var rs2);
                            return (rs1, rs2);
                        }
                        , LazyThreadSafetyMode.PublicationOnly);
                }
            }
        }
    }
}
